#![no_std]
#![doc = "RK3588 GMAC configuration helpers derived from U-Boot's dwc_eth_qos driver"]

use core::ptr::{read_volatile, write_volatile};

/// Identifier for the integrated GMAC instances on the RK3588.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GmacId {
    Gmac0,
    Gmac1,
}

impl GmacId {
    #[inline]
    const fn index(self) -> u32 {
        match self {
            GmacId::Gmac0 => 0,
            GmacId::Gmac1 => 1,
        }
    }
}

/// Supported PHY interface wiring modes.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PhyInterface {
    Rgmii,
    RgmiiId,
    RgmiiRxId,
    RgmiiTxId,
    Rmii,
}

/// Link speeds supported by the GMAC block.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LinkSpeed {
    Speed10,
    Speed100,
    Speed1000,
}

/// Error conditions returned by the driver.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Error {
    /// Requested configuration is incompatible with the selected PHY interface.
    InvalidSpeedForInterface,
}

/// Convenience alias for driver results.
pub type Result<T> = core::result::Result<T, Error>;

/// Delay configuration for RGMII clock lines.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Delay {
    /// Transmit clock delay tap value.
    pub tx: u8,
    /// Receive clock delay tap value.
    pub rx: u8,
}

impl Delay {
    /// Creates a new delay configuration.
    pub const fn new(tx: u8, rx: u8) -> Self {
        Self { tx, rx }
    }
}

/// Low-level access wrapper for a single RK3588 GMAC controller.
#[derive(Debug)]
pub struct Rk3588Gmac {
    id: GmacId,
    regs: usize,
    sys_grf: usize,
    php_grf: usize,
}

impl Rk3588Gmac {
    /// Creates a new GMAC wrapper from already mapped virtual addresses.
    ///
    /// # Safety
    ///
    /// The caller must ensure the provided addresses are valid and mapped with
    /// the required device attributes.
    pub const unsafe fn new(
        id: GmacId,
        regs_base: usize,
        sys_grf_base: usize,
        php_grf_base: usize,
    ) -> Self {
        Self {
            id,
            regs: regs_base,
            sys_grf: sys_grf_base,
            php_grf: php_grf_base,
        }
    }

    /// Returns the identifier of this GMAC instance.
    #[inline]
    pub const fn id(&self) -> GmacId {
        self.id
    }

    /// Returns the mapped register base.
    #[inline]
    pub const fn regs_base(&self) -> usize {
        self.regs
    }

    /// Configures the GMAC clocks and interface mode for RGMII variants.
    pub fn configure_rgmii(&self, interface: PhyInterface, delay: Delay) -> Result<()> {
        use PhyInterface::*;
        let id = self.id.index();

        let (tx_delay, rx_delay, enable_tx_delay, enable_rx_delay) = match interface {
            Rgmii => (delay.tx, delay.rx, true, true),
            RgmiiId => (0, 0, false, false),
            RgmiiRxId => (delay.tx, 0, true, false),
            RgmiiTxId => (0, delay.rx, false, true),
            Rmii => return Err(Error::InvalidSpeedForInterface),
        };

        self.write_php_grf(PHP_GRF_GMAC_CON0, gmac_phy_intf_sel_rgmii(id));
        self.write_php_grf(PHP_GRF_CLK_CON1, gmac_clk_rgmii_mode(id));

        let delay_enable = gmac_delay_enable(id, enable_tx_delay, enable_rx_delay);
        self.write_sys_grf(SYS_GRF_GMAC_CON7, delay_enable);

        let offset = if id == 1 {
            SYS_GRF_GMAC_CON9
        } else {
            SYS_GRF_GMAC_CON8
        };
        let delay_cfg = gmac_delay_cfg(tx_delay, rx_delay);
        self.write_sys_grf(offset, delay_cfg);
        Ok(())
    }

    /// Configures the GMAC clocks and interface mode for RMII.
    pub fn configure_rmii(&self) {
        let id = self.id.index();
        self.write_php_grf(PHP_GRF_GMAC_CON0, gmac_phy_intf_sel_rmii(id));
        self.write_php_grf(PHP_GRF_CLK_CON1, gmac_clk_rmii_mode(id));
    }

    /// Selects internal or external RMII reference clocks and gates.
    pub fn set_clock_selection(&self, clock_input: bool, enable: bool) {
        let id = self.id.index();
        let select = if clock_input {
            gmac_clk_select_io(id)
        } else {
            gmac_clk_select_cru(id)
        };
        let gate = if enable {
            gmac_clk_rmii_nogate(id)
        } else {
            gmac_clk_rmii_gate(id)
        };

        self.write_php_grf(PHP_GRF_CLK_CON1, select | gate);
    }

    /// Updates the GMAC clock divisors according to the negotiated link speed.
    pub fn set_speed(&self, interface: PhyInterface, speed: LinkSpeed) -> Result<()> {
        use LinkSpeed::*;
        use PhyInterface::*;

        let id = self.id.index();
        let value = match (interface, speed) {
            (Rmii, Speed10) => gmac_clk_rmii_div20(id),
            (Rmii, Speed100) => gmac_clk_rmii_div2(id),
            (Rmii, Speed1000) => return Err(Error::InvalidSpeedForInterface),
            (Rgmii | RgmiiId | RgmiiRxId | RgmiiTxId, Speed10) => gmac_clk_rgmii_div50(id),
            (Rgmii | RgmiiId | RgmiiRxId | RgmiiTxId, Speed100) => gmac_clk_rgmii_div5(id),
            (Rgmii | RgmiiId | RgmiiRxId | RgmiiTxId, Speed1000) => gmac_clk_rgmii_div1(id),
        };

        self.write_php_grf(PHP_GRF_CLK_CON1, value);
        Ok(())
    }

    /// Reads back a raw 32-bit value from the SYS_GRF block.
    pub fn read_sys_grf(&self, offset: usize) -> u32 {
        unsafe { read_volatile((self.sys_grf + offset) as *const u32) }
    }

    /// Reads back a raw 32-bit value from the PHP_GRF block.
    pub fn read_php_grf(&self, offset: usize) -> u32 {
        unsafe { read_volatile((self.php_grf + offset) as *const u32) }
    }

    fn write_sys_grf(&self, offset: usize, value: u32) {
        unsafe { write_volatile((self.sys_grf + offset) as *mut u32, value) };
    }

    fn write_php_grf(&self, offset: usize, value: u32) {
        unsafe { write_volatile((self.php_grf + offset) as *mut u32, value) };
    }
}

const SYS_GRF_GMAC_CON7: usize = 0x031c;
const SYS_GRF_GMAC_CON8: usize = 0x0320;
const SYS_GRF_GMAC_CON9: usize = 0x0324;

const PHP_GRF_GMAC_CON0: usize = 0x0008;
const PHP_GRF_CLK_CON1: usize = 0x0070;

#[inline]
const fn grf_bit(bit: u8) -> u32 {
    (1u32 << bit) | (1u32 << (bit + 16))
}

#[inline]
const fn grf_clr_bit(bit: u8) -> u32 {
    1u32 << (bit + 16)
}

#[inline]
const fn hiword_update(val: u32, mask: u32, shift: u8) -> u32 {
    (val << shift) | (mask << (shift + 16))
}

#[inline]
const fn gmac_phy_intf_sel_rgmii(id: u32) -> u32 {
    let base = 3 + id * 6;
    grf_bit(base as u8) | grf_clr_bit((base + 1) as u8) | grf_clr_bit((base + 2) as u8)
}

#[inline]
const fn gmac_phy_intf_sel_rmii(id: u32) -> u32 {
    let base = 3 + id * 6;
    grf_clr_bit(base as u8) | grf_clr_bit((base + 1) as u8) | grf_bit((base + 2) as u8)
}

#[inline]
const fn gmac_clk_rgmii_mode(id: u32) -> u32 {
    grf_clr_bit((5 * id) as u8)
}

#[inline]
const fn gmac_clk_rmii_mode(id: u32) -> u32 {
    grf_bit((5 * id) as u8)
}

#[inline]
const fn gmac_clk_select_cru(id: u32) -> u32 {
    grf_bit((5 * id + 4) as u8)
}

#[inline]
const fn gmac_clk_select_io(id: u32) -> u32 {
    grf_clr_bit((5 * id + 4) as u8)
}

#[inline]
const fn gmac_clk_rmii_div2(id: u32) -> u32 {
    grf_bit((5 * id + 2) as u8)
}

#[inline]
const fn gmac_clk_rmii_div20(id: u32) -> u32 {
    grf_clr_bit((5 * id + 2) as u8)
}

#[inline]
const fn gmac_clk_rgmii_div1(id: u32) -> u32 {
    grf_clr_bit((5 * id + 2) as u8) | grf_clr_bit((5 * id + 3) as u8)
}

#[inline]
const fn gmac_clk_rgmii_div5(id: u32) -> u32 {
    grf_bit((5 * id + 2) as u8) | grf_bit((5 * id + 3) as u8)
}

#[inline]
const fn gmac_clk_rgmii_div50(id: u32) -> u32 {
    grf_clr_bit((5 * id + 2) as u8) | grf_bit((5 * id + 3) as u8)
}

#[inline]
const fn gmac_clk_rmii_gate(id: u32) -> u32 {
    grf_bit((5 * id + 1) as u8)
}

#[inline]
const fn gmac_clk_rmii_nogate(id: u32) -> u32 {
    grf_clr_bit((5 * id + 1) as u8)
}

#[inline]
const fn gmac_delay_enable(id: u32, tx: bool, rx: bool) -> u32 {
    let tx_bit = (2 * id + 2) as u8;
    let rx_bit = (2 * id + 3) as u8;
    let mut value = 0;

    value |= if tx {
        grf_bit(tx_bit)
    } else {
        grf_clr_bit(tx_bit)
    };
    value |= if rx {
        grf_bit(rx_bit)
    } else {
        grf_clr_bit(rx_bit)
    };
    value
}

#[inline]
const fn gmac_delay_cfg(tx_delay: u8, rx_delay: u8) -> u32 {
    hiword_update(tx_delay as u32, 0xFF, 0) | hiword_update(rx_delay as u32, 0xFF, 8)
}
